module Tablero where

import Char
import Maybe

data Color = Blanco | Negro deriving (Show, Eq, Ord)
type Posicion = (Char, Int) 
data Tablero = T (Posicion -> Maybe Color)

-- Ejercicio 1: tablero vacío
-- Crea un tablero con una función que siempre devuelve Nothing, ya que
-- Nothing representa que no hay una ficha en la posición.
vacio :: Tablero
vacio = T (\ _ -> Nothing)

-- Ejercicio 1: tablero inicial
-- Crea un tablero con una función que devuelve Negro para las posiciones
-- (d,4) y (e,5), y Blanco para las posiciones (d,5) y (e,4) 
tableroInicial :: Tablero
tableroInicial = T disposicionInicial	
	where disposicionInicial p = case p of
		('d',4) -> Just Negro
		('e',5) -> Just Negro
		('e',4) -> Just Blanco
		('d',5) -> Just Blanco
		_ -> Nothing

-- Ejercicio 2: contenido
-- Devuelve el contenido de la posición evaluando la función del tablero
contenido :: Posicion -> Tablero -> Maybe Color
contenido p (T f) = f p

-- Ejercicio 2: poner
-- Pone la ficha en la posición indicada a través de la creación de una
-- nueva función que devuelve el color indicado para la nueva posición,
-- y para el resto de las posiciones devuelve los valores del tablero anterior
poner :: Posicion -> Color -> Tablero -> Tablero
poner p c (T f) = T g
	where 
		g x | x == p = Just c
			| otherwise = f x 

-- Ejercicio 3: desplazar columna
-- Devuelve la posición desplazada un cierto offset indicado. El valor
-- de la columna se calcula buscando el ascii de la letra de la posición
-- dada, se le suma el offset y se transforma nuevamente a char.
desplazarColumna :: Int -> Posicion -> Posicion
desplazarColumna x (a,i) = (chr(ord(a)+x),i)

-- Ejercicio 3: desplazar fila
-- Devuelve la posición desplazada un cierto offset indicado. El valor
-- nuevo de la fila es la suma de la fila anterior con el offset.
desplazarFila :: Int -> Posicion -> Posicion
desplazarFila x (a,i) = (a,i+x)

-- ** Función auxiliar **
-- Desplaza la fila y la columna para lograr un desplazamiento diagonal
-- hacia filas y columnas mayores
desplazarDiagonalA :: Int -> Posicion -> Posicion
desplazarDiagonalA x = (desplazarColumna x) . (desplazarFila x)

-- ** Función auxiliar **
-- Desplaza la fila y la columna para lograr un desplazamiento diagonal
-- hacia filas menores y columnas mayores
desplazarDiagonalB :: Int -> Posicion -> Posicion
desplazarDiagonalB x = (desplazarColumna x) . (desplazarFila (-x))

-- Ejercicio 4: generar
-- Genera las posiciones tomando las posiciones válidas de la lista infinita
-- de posiciones generada al iterar con la función de desplazamiento dada
generar :: Posicion -> (Posicion -> Posicion) -> [Posicion]
generar p f | posicionValida p = p:(takeWhile posicionValida (iterate f (f p)))
            | otherwise = []

-- ** Función auxiliar **
-- Devuelve True si la posición pertenece al tablero
posicionValida :: Posicion -> Bool
posicionValida (a,i) = 'a' <= a && a <= 'h' && 1 <= i && i <= 8

-- Ejercicio 5: posiciones a invertir
-- Calcula qué posiciones se deben invertir por la jugada hecha. Para ello,
-- por cada desplazamiento posible en el tablero:
--   - Toma las fichas del color contrario que están en las posiciones
--     generadas para la función de desplazamiento.
--   - Si dejó de tomar fichas porque había una ficha del mismo jugador que
--     realizó la jugada, entonces las posiciones se deben invertir. En otro
--     caso, no se deben invertir y el desplazamiento no aporta fichas.
posicionesAInvertir :: Posicion -> Tablero -> [Posicion]
posicionesAInvertir posJugada (T color) = concat [ if puedeInvertir (posibles desp) desp then (posibles desp) else [] | desp <- desplazamientos ]
	where otroColor pos = (color pos) == Just (colorContrario (fromJust (color posJugada)))
	      posibles desplazamiento = takeWhile otroColor (tail(generar posJugada desplazamiento))
	      puedeInvertir fichas desplazamiento = null fichas || color (desplazamiento (last fichas)) == color posJugada
	      desplazamientos = [ desp x | x <- [1,-1], desp <- [desplazarColumna,desplazarFila,desplazarDiagonalA,desplazarDiagonalB] ] 

-- ** Función auxiliar **
-- Devuelve el color inverso al pasado como argumento
colorContrario :: Color -> Color
colorContrario Blanco = Negro
colorContrario Negro  = Blanco

-- Ejercicio 6: invertir todas
-- Invierte las fichas en las posiciones dadas mediante una función lambda
-- que devuelve el color contrario si la posición dada está en la lista. En
-- otro caso devuelve lo que había en el tablero previamente 
invertirTodas :: [Posicion] -> Tablero -> Tablero
invertirTodas posiciones (T color) = T (\x -> if x `elem` posiciones then Just (colorContrario (fromJust (color x))) else color x) 

instance Show Tablero where
  show (T tablero) = 
      "   a b c d e f g h  \n" ++
      "  ----------------- \n" ++
      concatMap showFil [8,7..1] ++
      "  ----------------- \n" ++
      "   a b c d e f g h  \n"
    where
      showFil fil = show fil ++ " " ++
                    concatMap (showCol fil) ['a'..'h'] ++ "| " ++
                    show fil ++ "\n"
      showCol fil col = "|" ++ p (tablero (col,fil))
      p Nothing = " "
      p (Just Blanco) = "B"
      p (Just Negro) = "N"

